import * as Cesium from "cesium"
import type { CameraTraceFile, CesiumRoamingEventType } from "../types"

const EMPTY_CAMERA_TRACE_FILE = {
  id: undefined,
  data: [],
  interval: 16.667,
}
/**
 * cesium漫游录制及回放
 */
export class Roaming {
  //记录键盘按下和弹起的状态
  public keyboardControlState = {
    //用于跟踪按键触发状态
    keyState: {
      KeyW: false,
      KeyS: false,
      KeyA: false,
      KeyD: false,
      KeyQ: false,
      KeyE: false,
      ArrowUp: false,
      ArrowDown: false,
    },
    step: 10, //移动步长
    rotateRate: 0.1, //旋转步长
  }

  //回调事件集合
  private CallbackEvents = {
    //每一帧读取键盘状态，控制相机移动旋转
    KEYBOARD_ROAMING_EVENT_CALLBACK: () => {
      const camera = this.viewer.camera
      let moveAmount = this.keyboardControlState.step
      // 检查按键状态并移动相机
      if (this.keyboardControlState.keyState["KeyW"]) {
        camera.move(
          this.getCameraHorizontalDirection(camera).HorizontalForwardDirection,
          moveAmount,
        )
      }
      if (this.keyboardControlState.keyState["KeyS"]) {
        camera.move(
          this.getCameraHorizontalDirection(camera).HorizontalForwardDirection,
          -moveAmount,
        )
      }
      if (this.keyboardControlState.keyState["KeyA"]) {
        camera.move(
          this.getCameraHorizontalDirection(camera).verticalRightDirection,
          moveAmount,
        )
      }
      if (this.keyboardControlState.keyState["KeyD"]) {
        camera.move(
          this.getCameraHorizontalDirection(camera).verticalRightDirection,
          -moveAmount,
        )
      }
      if (this.keyboardControlState.keyState.KeyQ) {
        this.rotateCamera(camera, -this.keyboardControlState.rotateRate)
      }
      if (this.keyboardControlState.keyState.KeyE) {
        this.rotateCamera(camera, this.keyboardControlState.rotateRate)
      }
      if (this.keyboardControlState.keyState.ArrowUp) {
        this.liftCamera(camera, moveAmount)
      }
      if (this.keyboardControlState.keyState.ArrowDown) {
        this.liftCamera(camera, -moveAmount)
      }
    },
    //键盘按下事件回调
    KEYDOWN_CALLBACK: (event: any) => {
      switch (event.code) {
        case "KeyW": // Move forward
          this.keyboardControlState.keyState["KeyW"] = true
          break
        case "KeyS": // Move backward
          this.keyboardControlState.keyState["KeyS"] = true
          break
        case "KeyA": // Move left
          this.keyboardControlState.keyState["KeyA"] = true
          break
        case "KeyD": // Move right
          this.keyboardControlState.keyState["KeyD"] = true
          break
        case "KeyQ": // Rotate left
          this.keyboardControlState.keyState["KeyQ"] = true
          break
        case "KeyE": // Rotate right
          this.keyboardControlState.keyState["KeyE"] = true
          break
        case "ArrowRight":
          this.keyboardControlState.step += 1
          if (this.eventHandlers.stepChangeEventHandler) {
            this.eventHandlers.stepChangeEventHandler(
              this.keyboardControlState.step,
            )
          }
          break
        case "ArrowLeft":
          this.keyboardControlState.step -= 1
          if (this.eventHandlers.stepChangeEventHandler) {
            this.eventHandlers.stepChangeEventHandler(
              this.keyboardControlState.step,
            )
          }
          break
        case "ArrowUp":
          this.keyboardControlState.keyState["ArrowUp"] = true
          break
        case "ArrowDown":
          this.keyboardControlState.keyState["ArrowDown"] = true
          break
      }
    },
    //键盘弹起事件
    KEYUP_CALLBACK: (event: any) => {
      switch (event.code) {
        case "KeyW": // Move forward
          this.keyboardControlState.keyState["KeyW"] = false
          break
        case "KeyS": // Move backward
          this.keyboardControlState.keyState["KeyS"] = false
          break
        case "KeyA": // Move left
          this.keyboardControlState.keyState["KeyA"] = false
          break
        case "KeyD": // Move right
          this.keyboardControlState.keyState["KeyD"] = false
          break
        case "KeyQ": // Rotate left
          this.keyboardControlState.keyState["KeyQ"] = false
          break
        case "KeyE": // Rotate right
          this.keyboardControlState.keyState["KeyE"] = false
          break
        case "ArrowUp":
          this.keyboardControlState.keyState["ArrowUp"] = false
          break
        case "ArrowDown":
          this.keyboardControlState.keyState["ArrowDown"] = false
          break
      }
    },
  }

  //记录漫游轨迹信息——用于保存
  cameraTraceFile: CameraTraceFile = EMPTY_CAMERA_TRACE_FILE

  private recordTimer: undefined | NodeJS.Timeout //保存录制时使用的timer
  private playTimer: undefined | NodeJS.Timeout //保存播放时使用的timer

  //播放游标
  playState = {
    currentFrameNum: 0, //当前帧
    totalFrameNum: 0, //总帧数
    percentage: 0, //进度百分数
  }

  eventHandlers: {
    [key: string]: ((e: number) => void) | (() => void) | undefined
  } = {
    stepChangeEventHandler: undefined, //step改变时触发
  }

  constructor(public viewer: Cesium.Viewer) {}

  on(key: CesiumRoamingEventType, callback: (e: number) => void) {
    switch (key) {
      case "stepChange":
        this.eventHandlers.stepChangeEventHandler = callback
        break
    }
  }

  //开始录制
  public startRecord() {
    //初始化缓存区
    this.initPlayState()
    this.initTraceFileBuffer()
    this._enableKeyboardRoaming()
    this.cameraInfoSampling(this.viewer.camera)
  }

  //结束录制并保存
  public stopRecord() {
    clearInterval(this.recordTimer)
    this._disableKeyboardRoaming()
    // this.frameThinning()
    this.saveCameraTraceFile()
  }

  //轨迹文件抽稀
  frameThinning() {
    const standardFrame = 30
    const standardTimeGap = 1000 / standardFrame
    //将录制文件抽稀到每秒10帧，优化文件体积
    let { data } = this.cameraTraceFile
    data = data.filter((item, index) => index % 2 === 0)
    this.cameraTraceFile.data = data
    const timeRange = data[data.length - 1][0] - data[0][0]
    const timeGap = timeRange / data.length
    if (timeGap < standardTimeGap + 10) {
      this.frameThinning()
    } else {
      return
    }
  }

  //轨迹文件插值
  frameInterpolation(fps: number) {
    //todo:将相机轨迹文件插值到目标帧数
    const data = this.cameraTraceFile.data
    //计算当前帧数
    const averageTimeGap = (data[data.length - 1][0] - data[0][0]) / data.length
    const currentFrameNum = Math.floor(1000 / averageTimeGap)
    const newData: [number, string][] = []
    if (currentFrameNum > fps - 10) {
      console.log("插值完成", this.cameraTraceFile)
      return
    }
    data.forEach((item, i) => {
      if (i == data.length - 1) {
        newData.push(data[i])
      } else {
        const timeStamp = Math.round((data[i][0] + data[i + 1][0]) / 2)
        const frontFrameCamera = JSON.parse(data[i][1])
        const nextFrameCamera = JSON.parse(data[i + 1][1])
        const middleFrameCamera = {
          destination: {
            x:
              (frontFrameCamera.destination.x + nextFrameCamera.destination.x) /
              2,
            y:
              (frontFrameCamera.destination.y + nextFrameCamera.destination.y) /
              2,
            z:
              (frontFrameCamera.destination.z + nextFrameCamera.destination.z) /
              2,
          },
          orientation: {
            heading:
              (frontFrameCamera.orientation.heading +
                nextFrameCamera.orientation.heading) /
              2,
            pitch:
              (frontFrameCamera.orientation.pitch +
                nextFrameCamera.orientation.pitch) /
              2,
            roll:
              (frontFrameCamera.orientation.roll +
                nextFrameCamera.orientation.roll) /
              2,
          },
        }
        const interpolateData: [number, string] = [
          timeStamp,
          JSON.stringify(middleFrameCamera),
        ]
        newData.push(data[i])
        newData.push(interpolateData)
      }
    })
    this.cameraTraceFile.data = newData
    this.frameInterpolation(fps)
  }

  //加载漫游轨迹文件
  public loadFile(file: CameraTraceFile): Roaming {
    this.cameraTraceFile = file
    // this.frameInterpolation(60)
    this.playState.totalFrameNum = file.data.length
    this.initPlayState()
    return this
  }

  //轨迹回放
  public play({
    loop = false,
    callback,
  }: {
    loop?: boolean
    callback?: (percentage: number) => void
  }) {
    if (!this.cameraTraceFile) {
      console.error("播放失败：未读取到漫游轨迹文件")
    } else {
      const { interval, data } = this.cameraTraceFile
      //开始动画
      this.playTimer = setInterval(() => {
        const { currentFrameNum } = this.playState
        const {
          destination,
          orientation: { heading, pitch, roll },
        } = JSON.parse(data[currentFrameNum][1])
        this.viewer.camera.setView({
          destination,
          orientation: { heading, pitch, roll },
        })
        if (
          this.playState.currentFrameNum ===
          this.playState.totalFrameNum - 1
        ) {
          if (loop) {
            this.initPlayState()
            this.play({
              loop,
              callback,
            })
          }
        } else {
          this.playState.currentFrameNum++
          this.playState.percentage = Math.round(
            (currentFrameNum / this.playState.totalFrameNum) * 100,
          )
          callback && callback(this.playState.percentage)
        }
      }, interval)
    }
  }

  //轨迹回放暂停
  public pause() {
    if (this.playTimer) {
      clearInterval(this.playTimer)
    }
  }

  //初始化播放进度
  private initPlayState() {
    this.playState.currentFrameNum = 0
    this.playState.percentage = 0
    //如果正在播放中，结束当前播放
    if (this.playTimer) {
      clearInterval(this.playTimer)
    }
  }

  //初始化录制条件
  private initTraceFileBuffer() {
    this.cameraTraceFile = EMPTY_CAMERA_TRACE_FILE
  }

  //相机状态采样，生成轨迹文件
  private cameraInfoSampling(camera: Cesium.Camera, interval: number = 16.667) {
    this.cameraTraceFile = { id: undefined, data: [], interval } //初始化相机漫游信息文件;
    this.recordTimer = setInterval(() => {
      const { heading, pitch, roll } = camera
      this.cameraTraceFile.data.push([
        Date.now(),
        JSON.stringify({
          destination: camera.position,
          orientation: { heading, pitch, roll },
        }),
      ])
    }, interval)
  }

  //保存采样的轨迹文件
  saveCameraTraceFile() {
    //1.初始化localStorage
    if (!localStorage.getItem("RoamRecords")) {
      localStorage.setItem("RoamRecords", JSON.stringify([]))
    }

    //2.保存到localStorage
    const RoamRecords = JSON.parse(localStorage.getItem("RoamRecords") as any)
    this.cameraTraceFile.id = new Date().toLocaleString()
    RoamRecords.push(this.cameraTraceFile)
    localStorage.setItem("RoamRecords", JSON.stringify(RoamRecords))

    //初始化轨迹文件缓存区
    this.initTraceFileBuffer()
  }

  //启用键盘漫游
  public _enableKeyboardRoaming() {
    // 监听键盘按下事件更新键盘状态
    document.addEventListener("keydown", this.CallbackEvents.KEYDOWN_CALLBACK)
    document.addEventListener("keyup", this.CallbackEvents.KEYUP_CALLBACK)

    //每帧读取键盘状态并控制相机移动旋转
    this.viewer.scene.preUpdate.addEventListener(
      this.CallbackEvents.KEYBOARD_ROAMING_EVENT_CALLBACK,
    )
  }

  //禁用键盘漫游
  public _disableKeyboardRoaming() {
    //解绑每帧读取键盘状态控制相机事件
    this.viewer.scene.preUpdate.removeEventListener(
      this.CallbackEvents.KEYBOARD_ROAMING_EVENT_CALLBACK,
    )

    //解绑键盘状态监听事件
    document.removeEventListener(
      "keydown",
      this.CallbackEvents.KEYDOWN_CALLBACK,
    )
    document.removeEventListener("keyup", this.CallbackEvents.KEYUP_CALLBACK)
  }

  //旋转相机
  private rotateCamera(camera: Cesium.Camera, deg: number) {
    camera.setView({
      orientation: {
        heading: camera.heading + Cesium.Math.toRadians(deg),
        pitch: camera.pitch,
        roll: camera.roll,
      },
    })
  }

  //垂直抬升相机
  private liftCamera(camera: Cesium.Camera, moveAmount: number) {
    //抬升相机
    const CameraCartoPosition = Cesium.Cartographic.fromCartesian(
      camera.position,
    )
    CameraCartoPosition.height += moveAmount
    const { longitude, latitude, height } = CameraCartoPosition
    const newCameraPosition = Cesium.Cartesian3.fromRadians(
      longitude,
      latitude,
      height,
    )
    camera.setView({
      destination: newCameraPosition,
      orientation: {
        heading: camera.heading,
        pitch: camera.pitch,
        roll: camera.roll,
      },
    })
  }

  //获取相机水平前向量和右向量;
  private getCameraHorizontalDirection(camera: Cesium.Camera) {
    // 获取相机的方向向量和位置向量
    let direction = camera.direction
    let position = camera.position

    // 计算地面法向量：由地球球心到相机位置
    let groundNormal = Cesium.Cartesian3.normalize(
      position,
      new Cesium.Cartesian3(),
    )

    // 计算方向向量在地面法向量方向上的分量
    let dotProduct = Cesium.Cartesian3.dot(direction, groundNormal)
    let verticalComponent = Cesium.Cartesian3.multiplyByScalar(
      groundNormal,
      dotProduct,
      new Cesium.Cartesian3(),
    )
    let horizontalVector = Cesium.Cartesian3.subtract(
      direction,
      verticalComponent,
      new Cesium.Cartesian3(),
    )

    // 归一化水平前向量
    let HorizontalForwardDirection = Cesium.Cartesian3.normalize(
      horizontalVector,
      new Cesium.Cartesian3(),
    )

    // 计算水平向量的垂直向量
    let verticalToHorizontal = Cesium.Cartesian3.cross(
      horizontalVector,
      direction,
      new Cesium.Cartesian3(),
    )

    // 归一化水平右向量向量
    let verticalRightDirection = Cesium.Cartesian3.normalize(
      verticalToHorizontal,
      new Cesium.Cartesian3(),
    )
    return { HorizontalForwardDirection, verticalRightDirection }
  }
}
